#include "Precompile.h"
#include "DX10RenderSystem.h"

namespace Graphics{
	DX10RenderSystem::DX10RenderSystem(RenderMode &mode):
		m_mainRenderTarget(NULL),m_mainDepthStencil(NULL),m_currentRasterizerState(NULL),
		m_mainDepthStencilTexture(NULL), m_mainRenderTargetTexture(NULL),RenderSystem(mode){
			m_deviceHandle = NULL;
	}

	DX10RenderSystem::~DX10RenderSystem(){
		cleanup();
	}

	void DX10RenderSystem::initialize(HWND hwnd){
		m_hWnd = hwnd;
		resetRenderSystem(m_renderMode);
		m_frameTimer->restart();
	}

	/// Sets up the render system according to the render mode parameter.
	void DX10RenderSystem::resetRenderSystem(RenderMode &mode){
		cleanup();
		m_renderMode = mode;

		UINT deviceFlags = 0;
#ifdef _DEBUG
		deviceFlags = D3D10_CREATE_DEVICE_DEBUG;  
#endif
		DXGI_SWAP_CHAIN_DESC swapDesc;
		swapDesc.BufferCount = 1;
	    swapDesc.BufferDesc.Width = m_renderMode.resolutionXY.first;
	    swapDesc.BufferDesc.Height = m_renderMode.resolutionXY.second;
	    swapDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	    swapDesc.BufferDesc.RefreshRate.Numerator = 60;
	    swapDesc.BufferDesc.RefreshRate.Denominator = 1;
	    swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	    swapDesc.OutputWindow = m_hWnd;
	    swapDesc.SampleDesc.Count = 1;
	    swapDesc.SampleDesc.Quality = 0;
		swapDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL;
		swapDesc.Windowed = !m_renderMode.fullscreen;
		swapDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

		// Create device
		if(FAILED(D3D10CreateDeviceAndSwapChain(NULL,D3D10_DRIVER_TYPE_HARDWARE,NULL,deviceFlags,D3D10_SDK_VERSION,&swapDesc,&m_swapChain,&m_deviceHandle))){
			// Error handling
			LOG_ERROR("DX10RenderSystem::resetRenderSystem - Failed to create graphics device and or swap chain.");
			assert(0);
		}

		boost::static_pointer_cast<DX10RenderContext>(m_renderContext)->m_device = m_deviceHandle;
		RenderSystem::resetDevice();
		setRasterizerState();

		// Find multisample capabilities
		UINT SampleCount = getSampleCount();
		UINT numQualityLevels = 0;
		if(FAILED(m_deviceHandle->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM,SampleCount,&numQualityLevels))){
			// Error handling
			LOG_ERROR("DX10RenderSystem::resetRenderSystem - Failed to check multisampling.");
			assert(0);
		}

		D3D10_TEXTURE2D_DESC desc;
		ZeroMemory( &desc, sizeof(desc) );
		desc.Width = m_renderMode.resolutionXY.first;
		desc.Height = m_renderMode.resolutionXY.second;
		desc.MipLevels = 1;
		desc.ArraySize = 1;
		desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		desc.SampleDesc.Count = SampleCount;
		desc.SampleDesc.Quality = numQualityLevels-1;
		desc.Usage = D3D10_USAGE_DEFAULT;
		desc.BindFlags = D3D10_BIND_RENDER_TARGET;

		// Create the multisampled render target texture
		if(FAILED(m_deviceHandle->CreateTexture2D(&desc, NULL, &m_mainRenderTargetTexture))){
			// Error handling
			LOG_ERROR("DX10RenderSystem::resetRenderSystem - Failed to create multisampled render target texture.");
			assert(0);
		}

		D3D10_RENDER_TARGET_VIEW_DESC rtDesc;
		rtDesc.Format = desc.Format;
		rtDesc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2DMS;
		rtDesc.Texture2D.MipSlice = 0;

		// Create main render target
		if(FAILED(m_deviceHandle->CreateRenderTargetView(m_mainRenderTargetTexture, &rtDesc, &m_mainRenderTarget))){
			// Error handling
			LOG_ERROR("DX10RenderSystem::resetRenderSystem - Failed to create multisampled render target view.");
			assert(0);
		}

		// Create a back buffer
	    ID3D10Texture2D *pBackBuffer;
	    m_swapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), (LPVOID*)&pBackBuffer );

		// Create orig render target
		if(FAILED(m_deviceHandle->CreateRenderTargetView(pBackBuffer, NULL, &m_origRenderTarget))){
			// Error handling
			LOG_ERROR("DX10RenderSystem::resetRenderSystem - Failed to create back buffer.");
			assert(0);
		}

		// Create depth stencil texture.
	    desc.Width = m_renderMode.resolutionXY.first;
	    desc.Height = m_renderMode.resolutionXY.second;
	    desc.MipLevels = 1;
	    desc.Format = DXGI_FORMAT_D32_FLOAT;
	    desc.SampleDesc.Count = SampleCount;
	    desc.SampleDesc.Quality = numQualityLevels-1;
	    desc.Usage = D3D10_USAGE_DEFAULT;
	    desc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
	    desc.CPUAccessFlags = 0;
	    desc.MiscFlags = 0;
		if(FAILED(m_deviceHandle->CreateTexture2D(&desc, NULL, &m_mainDepthStencilTexture))){
			// Error handling
			LOG_ERROR("DX10RenderSystem::resetRenderSystem - Failed to create multisampled depth stencil texture.");
			assert(0);
		}

	    // Create the depth stencil view
	    D3D10_DEPTH_STENCIL_VIEW_DESC DescDS;
	    DescDS.Format = DXGI_FORMAT_D32_FLOAT;
	    DescDS.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2DMS;
		if(FAILED(m_deviceHandle->CreateDepthStencilView( m_mainDepthStencilTexture, &DescDS, &m_mainDepthStencil))){
			// Error handling
			LOG_ERROR("DX10RenderSystem::resetRenderSystem - Failed to create multisampled depth stencil view.");
			assert(0);
		}

		// Create orig depth stencil texture.
	    desc.Width = m_renderMode.resolutionXY.first;
	    desc.Height = m_renderMode.resolutionXY.second;
	    desc.MipLevels = 1;
	    desc.Format = DXGI_FORMAT_D32_FLOAT;
	    desc.SampleDesc.Count = 1;
	    desc.SampleDesc.Quality = 0;
	    desc.Usage = D3D10_USAGE_DEFAULT;
	    desc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
	    desc.CPUAccessFlags = 0;
	    desc.MiscFlags = 0;
		if(FAILED(m_deviceHandle->CreateTexture2D(&desc, NULL, &m_origDepthStencilTexture))){
			// Error handling
			LOG_ERROR("DX10RenderSystem::resetRenderSystem - Failed to create depth stencil texture.");
			assert(0);
		}

		DescDS.Format = DXGI_FORMAT_D32_FLOAT;
	    DescDS.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
		DescDS.Texture2D.MipSlice = 0;
		if(FAILED(m_deviceHandle->CreateDepthStencilView(m_origDepthStencilTexture, &DescDS, &m_origDepthStencil))){
			// Error handling
			LOG_ERROR("DX10RenderSystem::resetRenderSystem - Failed to create depth stencil view.");
			assert(0);
		}

		ID3D10RenderTargetView* aRTViews[ 1 ] = { m_origRenderTarget };
		m_deviceHandle->OMSetRenderTargets(1,aRTViews,m_origDepthStencil);

		setViewport();
	}

	void DX10RenderSystem::beginFrame(){
		float ClearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };

		// Clear original render targets
		m_deviceHandle->ClearDepthStencilView(m_origDepthStencil, D3D10_CLEAR_DEPTH, 1.0, 0);
		m_deviceHandle->ClearRenderTargetView(m_origRenderTarget, ClearColor);

		// Set the multisampled render targets.
		ID3D10RenderTargetView* aRTViews[ 1 ] = { m_mainRenderTarget };
		m_deviceHandle->OMSetRenderTargets(1,aRTViews,m_mainDepthStencil);

		float ClearColor2[4] = { 0.25f, 0.31f, 0.42f, 1.0f };
		// Clear the render target and DSV
	    m_deviceHandle->ClearRenderTargetView( m_mainRenderTarget, ClearColor2 );
	    m_deviceHandle->ClearDepthStencilView( m_mainDepthStencil, D3D10_CLEAR_DEPTH, 1.0, 0 );
	}

	void DX10RenderSystem::endFrame(){
		// Copy it over because we can't resolve on present
	    ID3D10Resource* pRT;
	    m_origRenderTarget->GetResource(&pRT);
	    D3D10_RENDER_TARGET_VIEW_DESC rtDesc;
	    m_origRenderTarget->GetDesc(&rtDesc);
	    m_deviceHandle->ResolveSubresource(pRT, D3D10CalcSubresource(0, 0, 1), m_mainRenderTargetTexture, D3D10CalcSubresource(0, 0, 1), rtDesc.Format);
		pRT->Release();

		// Use original render targets again
		ID3D10RenderTargetView* aRTViews[ 1 ] = { m_origRenderTarget };
	    m_deviceHandle->OMSetRenderTargets(1, aRTViews, m_origDepthStencil);

		PostRenderEvent.invoke();

		// Present rendered image
		m_swapChain->Present(m_renderMode.vsync,0);
	}

	void DX10RenderSystem::update(){
		RenderSystem::update();

		beginFrame();
		RenderingEvent.invoke(m_renderContext.get());
		endFrame();
	}

	/// Cleanup rendersystem resources and devices
	void DX10RenderSystem::cleanup(){
		if(m_deviceHandle)
			m_deviceHandle->Release();
		if(m_mainRenderTarget)
			m_mainRenderTarget->Release();
		if(m_mainDepthStencil)
			m_mainDepthStencil->Release();
		if(m_currentRasterizerState)
			m_currentRasterizerState->Release();
		if(m_mainDepthStencilTexture)
			m_mainDepthStencilTexture->Release();
		if(m_mainRenderTargetTexture)
			m_mainRenderTargetTexture->Release();
	}

	/// Gets translated multisample value
	UINT DX10RenderSystem::getSampleCount(){
		switch(m_renderMode.multisampling){
		case MS_1X:
			return 1;
		case MS_2X:
			return 2;
		case MS_4X:
			return 4;
		case MS_8X:
			return 8;
		case MS_16X:
			return 16;
		}
		return 1;
	}

	///	Sets the viewport from the current rendermode.
	void DX10RenderSystem::setViewport(){
		D3D10_VIEWPORT vp;
	    vp.Width = m_renderMode.resolutionXY.first;
	    vp.Height = m_renderMode.resolutionXY.second;
	    vp.MinDepth = 0.0f;
	    vp.MaxDepth = 1.0f;
	    vp.TopLeftX = 0;
	    vp.TopLeftY = 0;
	    m_deviceHandle->RSSetViewports( 1, &vp );
	}

	///	Sets the rasterizer state from the current rendermode.
	void DX10RenderSystem::setRasterizerState(){
	    D3D10_RASTERIZER_DESC CurrentRasterizerState;
	    CurrentRasterizerState.FillMode = D3D10_FILL_SOLID;
	    CurrentRasterizerState.CullMode = D3D10_CULL_FRONT;
	    CurrentRasterizerState.FrontCounterClockwise = true;
	    CurrentRasterizerState.DepthBias = false;
	    CurrentRasterizerState.DepthBiasClamp = 0;
	    CurrentRasterizerState.SlopeScaledDepthBias = 0;
	    CurrentRasterizerState.DepthClipEnable = true;
	    CurrentRasterizerState.ScissorEnable = false;
		if(m_renderMode.multisampling != MS_1X)
	    	CurrentRasterizerState.MultisampleEnable = true;
		else
			CurrentRasterizerState.MultisampleEnable = false;
	    CurrentRasterizerState.AntialiasedLineEnable = false;

		ID3D10RasterizerState* currentState = NULL;
		if(FAILED(m_deviceHandle->CreateRasterizerState(&CurrentRasterizerState, &currentState))){
			// Error handling
			LOG_ERROR("DX10RenderSystem::setRasterizerState - Failed to create rasterizer state.");
			assert(0);
		}
	    m_deviceHandle->RSSetState(currentState);

		if(m_currentRasterizerState)
			m_currentRasterizerState->Release();
		m_currentRasterizerState = currentState;
	}
}